Stage 4
Biowulf2
cd /data/OGVFB_BG/scEiaD/2024_02_28/snakeout/hs111_mature_eye_full/neural_cells
mamba deactivate; mamba activate; bash ~/git/scEiaD_modeling/Snakemake.wrapper.sh ~/git/scEiaD_modeling/workflow/Snakefile ~/git/scEiaD_modeling/config/config_hs111_mature_eye_full__neural.yaml ~/git/scEiaD_modeling/config/cluster.json
Assess Output
library(tidyverse)
source('analysis_scripts.R')
obs_neural <- pull_obs('~/data/scEiaD_modeling/hs111_mature_eye_neural/hs111_mature_eye_20240924_full__neural5000hvg_200e_50l.obs.csv.gz', machine_label = 'MCT_scANVI_step4', drop_col = FALSE)
`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.
diff_neural <- pull_diff("~/data/scEiaD_modeling/hs111_mature_eye_neural/hs111_mature_eye_20240924_full__neural5000hvg_200e_50l.difftesting.leiden3.csv.gz")
'select()' returned 1:many mapping between keys and columns
Warning: Detected an unexpected many-to-many relationship between `x` and `y`.Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
Ratio (percentage) of labelled cell types for each leiden3
cluster
obs_neural$labels %>%
arrange(mCT) %>%
mutate(leiden3 = as.factor(leiden3)) %>%
DT::datatable(filter = 'top')
Mixed clusters
obs_neural$labels %>%
filter(grepl(",", mMCT)) %>%
arrange(mCT) %>%
mutate(leiden3 = as.factor(leiden3)) %>%
DT::datatable(filter = 'top')
UMAP Plots
obs_neural$obs %>%
left_join(obs_neural$labels, by = 'leiden3') %>%
ggplot(aes(x=umap1,y=umap2)) +
scattermore::geom_scattermore(aes(color = MCT_scANVI_step4), pointsize = 0.8, alpha = 0.5) +
ggrepel::geom_label_repel(data = . %>% group_by(MCT_scANVI_step4) %>%
summarise(umap1 = median(umap1),
umap2 = median(umap2)),
aes(label = MCT_scANVI_step4, color = MCT_scANVI_step4)) +
scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) +
cowplot::theme_cowplot() + theme(legend.position = "none")

obs_neural$obs %>%
left_join(obs_neural$labels, by = 'leiden3') %>%
ggplot(aes(x=umap1,y=umap2)) +
scattermore::geom_scattermore(aes(color = MCT_scANVI), pointsize = 0.8, alpha = 0.5) +
ggrepel::geom_label_repel(data = . %>% group_by(leiden3) %>%
summarise(umap1 = median(umap1),
umap2 = median(umap2)),
aes(label = leiden3)) +
scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) +
cowplot::theme_cowplot() + theme(legend.position = "none")

obs_neural$obs %>%
left_join(obs_neural$labels, by = 'leiden3') %>%
ggplot(aes(x=umap1,y=umap2)) +
scattermore::geom_scattermore(aes(color = studyRatio), pointsize = 0.7) +
scale_color_viridis_c() +
cowplot::theme_cowplot()

obs_neural$obs %>%
left_join(obs_neural$labels, by = 'leiden3') %>%
ggplot(aes(x=umap1,y=umap2)) +
scattermore::geom_scattermore(aes(color = mMCT), pointsize = 0.8, alpha = 0.5) +
ggrepel::geom_label_repel(data = . %>% group_by(mMCT) %>%
summarise(umap1 = median(umap1),
umap2 = median(umap2)),
aes(label = mMCT, color = mMCT)) +
scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) +
cowplot::theme_cowplot() + theme(legend.position = "none")

NA
NA
hclust
Take pseudobulk values (at the cluster level) and hierarchically
cluster them to ensure there aren’t any issues in either the overall
structure (e.g. rod and cones are intersperse)d and/or to identify any
potential mislabeled clusters
pb <- data.table::fread('~/data/scEiaD_modeling/hs111_mature_eye_neural/hs111_mature_eye_20240924_full__neural5000hvg_200e_50l.pseudoBulk.leiden3.csv.gz')
colnames(pb) <- gsub("\\.\\d+","",colnames(pb))
hvg <- data.table::fread('~/data/scEiaD_modeling/hs111_mature_eye_neural/hvg5000.csv.gz')[-1,]
rnames <- pb$V1
clust <- str_extract(rnames, '\\d+') %>% as.integer()
pb <- pb[,-1] %>% as.matrix()
row.names(pb) <- as.character(clust)
pb <- pb[as.character(obs_neural$labels$leiden3),]
pb_norm <- metamoRph::normalize_data(t(pb), sample_scale = 'cpm') %>% t()
Sample CPM scaling
log1p scaling
# remove cell cycle genes
conv_table <- AnnotationDbi::select(org.Hs.eg.db::org.Hs.eg.db,
keys=gsub('\\.\\d+','',unique(colnames(pb_norm))),
columns=c("ENSEMBL","SYMBOL", "MAP","GENENAME", "ENTREZID"), keytype="ENSEMBL")
'select()' returned 1:many mapping between keys and columns
cc_genes <- hvg %>% mutate(ENSEMBL = gsub("\\.\\d+","",V2)) %>%
left_join(conv_table, by = "ENSEMBL") %>%
mutate(cc_genes = case_when(SYMBOL %in% (Seurat::cc.genes.updated.2019 %>% unlist()) ~ TRUE)) %>%
filter(cc_genes) %>% pull(V2)
ribo_genes <- hvg %>% mutate(ENSEMBL = gsub("\\.\\d+","",V2)) %>%
left_join(conv_table, by = "ENSEMBL") %>% filter(grepl("^RPL|^RPS|^MT",SYMBOL)) %>%
pull(SYMBOL)
pb_norm <- pb_norm[,hvg$V2]
#pb_norm <- pb_norm[,hvg$V2[!hvg$V2 %in% c(cc_genes,ribo_genes)]]
# https://stats.stackexchange.com/questions/31565/compute-a-cosine-dissimilarity-matrix-in-r
sim <- pb_norm / sqrt(rowSums(pb_norm * pb_norm))
sim <- sim %*% t(sim)
D_sim <- as.dist(1 - sim)
Found more than one class "dist" in cache; using the first, from namespace 'BiocGenerics'
Also defined by ‘spam’
hclust_sim <- hclust(D_sim, method = 'average')
hclust_sim$labels <- obs_neural$labels %>% pull(leiden3)
library(ggtree)
p <- ggtree(hclust_sim)
p$data <- p$data %>% left_join(obs_neural$labels, by = c("label" = "leiden3")) %>%
mutate(techRatio = round(techRatio, digits = ))
p + layout_dendrogram() +
geom_tiplab(aes(label = paste(label, mMCT, studyCount, TotalCount, techRatio, sep = ' - '), color = mCT)) +
theme_dendrogram(plot.margin=margin(16,16,300,16)) +
scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) +
guides(color="none")

p <- ggtree(hclust_sim)
p$data <- p$data %>% left_join(obs_neural$labels %>% mutate(studies = case_when(studyCount ==1 ~ studies,
TRUE ~ "multiple")), by = c("label" = "leiden3"))
p + layout_dendrogram() +
geom_tiplab(aes(label = paste(label, mMCT, studies, sep = ' - '), color = mCT)) +
geom_tippoint(aes(shape = studies), size= 3) +
theme_dendrogram(plot.margin=margin(16,16,300,16)) +
scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) +
guides(color="none")

p <- ggtree(hclust_sim)
p$data <- p$data %>% left_join(obs_neural$labels %>% mutate(studies = case_when(studyRatio ==1 ~ studiesRatio,
TRUE ~ "multiple")), by = c("label" = "leiden3"))
p + layout_dendrogram() +
geom_tiplab(aes(label = paste(label, mMCT, studies, sep = ' - '), color = mCT)) +
geom_tippoint(aes(shape = studies), size= 3) +
theme_dendrogram(plot.margin=margin(16,16,300,16)) +
scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) +
guides(color="none")

NA
NA
suspicious clusters
reasons:
- study specific clusters in “wrong” (not with like) parts of the
tree
- mixed cell types
sus_neural <- #c(71,93,126,112,144,39,21,85,135,7,74,36,124)
c(
# reason 1
c(99, 97, 38, 50,40, 98, 94),
# reason 2
c(85, 95, 91, 52, 77)
)
# to provide an additional layer of resolution to the cell type
hr_neural <- list()
UMAP Plots
obs_neural$obs %>%
left_join(obs_neural$labels, by = 'leiden3') %>%
ggplot(aes(x=umap1,y=umap2)) +
scattermore::geom_scattermore(aes(color = MCT_scANVI_step4), pointsize = 0.8, alpha = 0.5) +
scattermore::geom_scattermore(data = obs_neural$obs %>%
left_join(obs_neural$labels, by = 'leiden3') %>%
filter(leiden3 %in% sus_neural),
color = 'red', pointsize = 0.8, alpha = 0.5) +
ggrepel::geom_label_repel(data = . %>% group_by(leiden3) %>%
filter(leiden3 %in% sus_neural) %>%
summarise(umap1 = median(umap1),
umap2 = median(umap2)),
aes(label = leiden3)) +
scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) +
cowplot::theme_cowplot()

obs_neural$obs %>%
left_join(obs_neural$labels, by = 'leiden3') %>%
filter(!leiden3 %in% sus_neural) %>%
ggplot(aes(x=umap1,y=umap2)) +
scattermore::geom_scattermore(aes(color = mCT), pointsize = 0.8, alpha = 0.5) +
ggrepel::geom_label_repel(data = . %>% group_by(leiden3) %>%
filter(leiden3 %in% sus_neural) %>%
summarise(umap1 = median(umap1),
umap2 = median(umap2)),
aes(label = leiden3)) +
scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) +
cowplot::theme_cowplot()

Call CT
hr_neural <- list()
photoreceptor
Removing two more clusters with no ARR3 / RHO signal (and
suspicious hclust) above
https://www.nature.com/articles/s41598-020-66092-9
tib <- diff_neural$diff_testing %>%
left_join(obs_neural$labels, by = c('base'='leiden3')) %>%
filter(mCT %in% c('rod','cone'), !base %in% sus_neural) %>%
left_join(conv_table) %>%
filter(SYMBOL %in% c('ARR3','OPN1LW','OPN1SW','RHO', 'OPN1MW', 'RCVRN',"CRX","PROM1")) %>%
select(SYMBOL, base, logfoldchanges) %>%
pivot_wider(values_from = logfoldchanges, names_from = base)
Joining with `by = join_by(ENSEMBL)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
mat <- tib %>% select(-1) %>% as.matrix()
row.names(mat) <- tib %>% pull(1)
col_fun = circlize::colorRamp2(c(-5, 0, 5), c("blue", "white", "red"))
ComplexHeatmap::Heatmap(mat, col=col_fun)

sus_neural <- c(sus_neural,
c(86,29))
hr_neural$`cone (s)` <- c(87)
hr_neural$`cone (ml)` <- c(72,74,20,90,81)

obs_neural$obs %>%
left_join(obs_neural$labels, by = 'leiden3') %>%
filter(!leiden3 %in% sus_neural,
mCT %in% c("rod", "cone")) %>%
left_join(hr_neural %>% enframe(name = 'Cell Type', value = 'leiden3') %>% unnest(leiden3),
by = 'leiden3') %>%
mutate(`Cell Type` = case_when(is.na(`Cell Type`) ~ mCT,
TRUE ~ `Cell Type`)) %>%
ggplot(aes(x=umap1,y=umap2)) +
scattermore::geom_scattermore(aes(color = `Cell Type`), pointsize = 0.8, alpha = 0.5) +
ggrepel::geom_label_repel(data = . %>% group_by(leiden3) %>%
summarise(umap1 = median(umap1),
umap2 = median(umap2)),
aes(label = leiden3)) +
scale_color_manual(values =
c(pals::alphabet2(), pals::glasbey(), pals::alphabet(), pals::brewer.set1(n=12)) %>% unname()) +
cowplot::theme_cowplot()

bipolar

tib <- diff_neural$diff_testing %>%
left_join(obs_neural$labels, by = c('base'='leiden3')) %>%
filter(mCT %in% c('bipolar','rod bipolar'), !base %in% sus_neural) %>%
left_join(conv_table) %>%
filter(SYMBOL %in% c('PRKCA','GRM6','GRIK1','NIF3L1',
'LINC00470','DOK5','NELL2','STX18',
'ODF2L','FAM19A4','MEIS2','CALB1', 'FUT4',
'SCG2','LRPPRC','FEZF1')) %>%
select(SYMBOL, base, logfoldchanges) %>%
pivot_wider(values_from = logfoldchanges, names_from = base)
Joining with `by = join_by(ENSEMBL)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
mat <- tib %>% select(-1) %>% as.matrix()
row.names(mat) <- tib %>% pull(1)
col_fun = circlize::colorRamp2(c(-5, 0, 5), c("blue", "white", "red"))
ComplexHeatmap::Heatmap(mat, col=col_fun)

hr_neural$`bipolar (rod)` <- c(12,78,32)
hr_neural$`bipolar (off)` <- c(11,28,33,36,46,17)
hr_neural$`bipolar (on)` <- c(68,55,6,82,27,30,39)
obs_neural$obs %>%
left_join(obs_neural$labels, by = 'leiden3') %>%
filter(!leiden3 %in% sus_neural,
mCT %in% c("bipolar", "rod bipolar")) %>%
left_join(hr_neural %>% enframe(name = 'Cell Type', value = 'leiden3') %>% unnest(leiden3),
by = 'leiden3') %>%
ggplot(aes(x=umap1,y=umap2)) +
scattermore::geom_scattermore(aes(color = `Cell Type`), pointsize = 0.8, alpha = 0.5) +
ggrepel::geom_label_repel(data = . %>% group_by(leiden3) %>%
summarise(umap1 = median(umap1),
umap2 = median(umap2)),
aes(label = leiden3)) +
scale_color_manual(values =
c(pals::alphabet2(), pals::glasbey(),
pals::alphabet(), pals::brewer.set1(n=12)) %>% unname()) +
cowplot::theme_cowplot()

horizontal

diff_neural$diff_testing %>%
left_join(obs_neural$labels, by = c('base'='leiden3')) %>%
filter(mCT == 'horizontal') %>%
left_join(conv_table) %>%
filter(SYMBOL %in% c('ONECUT1','ONECUT2','LHX1','ISL1')) %>%
select(SYMBOL, base, logfoldchanges) %>%
ggplot(aes(x=as.factor(base), y=SYMBOL, color = logfoldchanges)) +
geom_point(size =10) +
scale_color_viridis_c()
Joining with `by = join_by(ENSEMBL)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.

tib <- diff_neural$diff_testing %>%
left_join(obs_neural$labels, by = c('base'='leiden3')) %>%
filter(mCT == 'horizontal') %>%
left_join(conv_table) %>%
filter(SYMBOL %in% c('ONECUT1','ONECUT2','LHX1','ISL1')) %>%
select(SYMBOL, base, logfoldchanges) %>%
pivot_wider(values_from = logfoldchanges, names_from = base)
Joining with `by = join_by(ENSEMBL)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
mat <- tib %>% select(-1) %>% as.matrix()
row.names(mat) <- tib %>% pull(1)
col_fun = circlize::colorRamp2(c(-4, 0, 4), c("blue", "white", "red"))
ComplexHeatmap::Heatmap(mat, col=col_fun)

hr_neural$`horizontal (h1)` <- c(18,70)
hr_neural$`horizontal (h2)` <- c(58)
obs_neural$obs %>%
left_join(obs_neural$labels, by = 'leiden3') %>%
filter(!leiden3 %in% sus_neural,
mCT %in% c("horizontal")) %>%
left_join(hr_neural %>% enframe(name = 'Cell Type', value = 'leiden3') %>% unnest(leiden3),
by = 'leiden3') %>%
ggplot(aes(x=umap1,y=umap2)) +
scattermore::geom_scattermore(aes(color = `Cell Type`), pointsize = 0.8, alpha = 0.5) +
ggrepel::geom_label_repel(data = . %>% group_by(leiden3) %>%
summarise(umap1 = median(umap1),
umap2 = median(umap2)),
aes(label = leiden3)) +
scale_color_manual(values =
c(pals::alphabet2(), pals::glasbey(),
pals::alphabet(), pals::brewer.set1(n=12)) %>% unname()) +
cowplot::theme_cowplot()

amacrine
Most amacrine cells are inhibitory neurons utilizing GABA or glycine
as neurotransmitters. By assessing the expression of marker genes for
GABAergic (glutamate carboxylase, GAD1 and GAD2) and glycinergic
(SLC6A9, encoding the high affinity glycine transporter GLYT1)
amacrines20, we identified 16 putative GABAergic and 8 putative
glycinergic amacrine cell types among a total of 25 types (Fig. 3a,b).
One type (C14) expressed none of these three genes at high levels, and
might correspond to a non-GABAergic non-Glycinergic (nGnG) type
identified in mouse2122 . One of the glycinergic types (C17) also
expressed GAD2, raising the possibility that it uses both
transmitters.
REMOVING CLUSTER 0 AS IT IS STUDY SPECIFIC AND NOT EXPRESSING
ANY KNOWN GABA/GLYCI MARKERS
tib <- diff_neural$diff_testing %>%
left_join(obs_neural$labels, by = c('base'='leiden3')) %>%
filter(mCT == 'amacrine') %>%
left_join(conv_table) %>%
filter(SYMBOL %in% c('GAD1','GAD2','SLC6A9','NFIA')) %>%
select(SYMBOL, base, logfoldchanges) %>%
pivot_wider(values_from = logfoldchanges, names_from = base)
Joining with `by = join_by(ENSEMBL)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
mat <- tib %>% select(-1) %>% as.matrix()
row.names(mat) <- tib %>% pull(1)
col_fun = circlize::colorRamp2(c(-5, 0, 5), c("blue", "white", "red"))
ComplexHeatmap::Heatmap(mat, col=col_fun)

sus_neural <- c(sus_neural,
c(0))
hr_neural$`amacrine (gabanergic)` <- c(76,67,56,59,37,62,73,15,69,44,75,83,24,66,22,26,80,41,61,91,42)
hr_neural$`amacrine (glycinergic)` <- c(25,16,88,43)
hr_neural$`amacrine (gaba/glyci)` <- c(91,31,65)
obs_neural$obs %>%
left_join(obs_neural$labels, by = 'leiden3') %>%
filter(!leiden3 %in% sus_neural,
mCT %in% c("amacrine")) %>%
left_join(hr_neural %>% enframe(name = 'Cell Type', value = 'leiden3') %>% unnest(leiden3),
by = 'leiden3') %>%
ggplot(aes(x=umap1,y=umap2)) +
scattermore::geom_scattermore(aes(color = `Cell Type`), pointsize = 0.8, alpha = 0.5) +
ggrepel::geom_label_repel(data = . %>% group_by(leiden3) %>%
summarise(umap1 = median(umap1),
umap2 = median(umap2)),
aes(label = leiden3)) +
scale_color_manual(values =
c(pals::alphabet2(), pals::glasbey(),
pals::alphabet(), pals::brewer.set1(n=12)) %>% unname()) +
cowplot::theme_cowplot()

retinal ganglion
These don’t match up to the published markers very well - for example
the parasol RG seem to be a mixed ON/OFF cluster. Should run a retinal
ganglion specific scVI run and see whether that does a better job.
tib <- diff_neural$diff_testing %>%
left_join(obs_neural$labels, by = c('base'='leiden3')) %>%
filter(mCT %in% c('retinal ganglion'), !base %in% sus_neural) %>%
left_join(conv_table) %>%
filter(SYMBOL %in% c('TPBG','TBR1','FABP4','CHRNA2', 'LMO2',
'EOMES','SSTR2','FOXP2','FOXP1','PRR35','CARTPT',
'CDKN2A','ARPP21','OPN4', 'NEFM',
'TUBB3')) %>%
select(SYMBOL, base, scores) %>%
pivot_wider(values_from = scores, names_from = base)
Joining with `by = join_by(ENSEMBL)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
mat <- tib %>% select(-1) %>% as.matrix()
row.names(mat) <- tib %>% pull(1)
col_fun = circlize::colorRamp2(c(-15, 0, 15), c("blue", "white", "red"))
ComplexHeatmap::Heatmap(mat, col=col_fun)

obs_neural$obs %>%
left_join(obs_neural$labels, by = 'leiden3') %>%
filter(mCT == 'retinal ganglion', SubCellType != '') %>%
left_join(obs_neural$labels) %>%
group_by(leiden3, SubCellType) %>%
summarise(Count = n()) %>%
mutate(Ratio = Count/sum(Count)) %>%
filter(Ratio > 0.5) %>% arrange(SubCellType)
Joining with `by = join_by(leiden3, TotalCount, mMCT, mCount, mMCT_Ratio, mCT, aMCT, aCount, aMCT_Ratio, a_Ratio_sum, subCT, subCount, subCT_Ratio, subRatio_sum, studyCount, studies, studyRatio, studiesRatio, tissueCount, tissues, tissueRatio, techRatio)``summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.
hr_neural$`retinal ganglion (ON midget)` <- c(45, 54,63, 92)
hr_neural$`retinal ganglion (OFF midget)` <- c(19,34,57,71,79, 89, 96)
hr_neural$`retinal ganglion (parasol)` <- c(64)

obs_neural$obs %>%
left_join(obs_neural$labels, by = 'leiden3') %>%
filter(!leiden3 %in% sus_neural,
mCT %in% c("retinal ganglion")) %>%
left_join(hr_neural %>% enframe(name = 'Cell Type', value = 'leiden3') %>% unnest(leiden3),
by = 'leiden3') %>%
mutate(`Cell Type` = case_when(is.na(`Cell Type`) ~ mCT,
TRUE ~ `Cell Type`)) %>%
ggplot(aes(x=umap1,y=umap2)) +
scattermore::geom_scattermore(aes(color = `Cell Type`), pointsize = 0.8, alpha = 1) +
ggrepel::geom_label_repel(data = . %>% group_by(leiden3) %>%
summarise(umap1 = median(umap1),
umap2 = median(umap2)),
aes(label = leiden3)) +
scale_color_manual(values =
c(pals::glasbey(), pals::glasbey(), pals::alphabet(), pals::brewer.set1(n=12)) %>% unname()) +
cowplot::theme_cowplot()

NEW
Update overall graphics on the new labels
UMAP
hr_long <- hr_neural %>% enframe(name = 'hrCT', value = 'leiden3') %>% unnest(leiden3)
obs_neural$nobs <- obs_neural$obs %>%
left_join(obs_neural$labels, by = 'leiden3') %>%
left_join(hr_long,
by = 'leiden3') %>%
filter(!leiden3 %in% (sus_neural)) %>%
mutate(CT = mCT,
hrCT = case_when(!is.na(hrCT) ~ hrCT,
TRUE ~ CT))
Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
obs_neural$nlabels <- obs_neural$labels %>%
left_join(hr_long,
by = 'leiden3') %>%
filter(!leiden3 %in% (sus_neural)) %>%
mutate(CT = mCT,
hrCT = case_when(!is.na(hrCT) ~ hrCT,
TRUE ~ CT))
obs_neural$nobs %>%
ggplot(aes(x=umap1,y=umap2)) +
scattermore::geom_scattermore(aes(color = CT), pointsize = 0.8, alpha = 0.5) +
ggrepel::geom_label_repel(data = . %>% group_by(CT) %>%
summarise(umap1 = median(umap1),
umap2 = median(umap2)),
aes(label = CT, color = CT)) +
scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) +
cowplot::theme_cowplot() + theme(legend.position = "none")

obs_neural$nobs %>%
ggplot(aes(x=umap1,y=umap2)) +
scattermore::geom_scattermore(aes(color = hrCT), pointsize = 0.8, alpha = 0.5) +
ggrepel::geom_label_repel(data = . %>% group_by(hrCT) %>%
summarise(umap1 = median(umap1),
umap2 = median(umap2)),
aes(label = hrCT, color = hrCT)) +
scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) +
cowplot::theme_cowplot() + theme(legend.position = "none")

obs_neural$nobs %>%
ggplot(aes(x=umap1,y=umap2)) +
scattermore::geom_scattermore(aes(color = as.factor(hrCT)), pointsize = 2.1, alpha = 0.5) +
ggrepel::geom_label_repel(data = . %>% group_by(CT, hrCT) %>%
summarise(umap1 = median(umap1),
umap2 = median(umap2)),
aes(label = hrCT, color = hrCT)) +
scale_color_manual(values = c(pals::alphabet2(), pals::glasbey(), pals::alphabet(), pals::brewer.set1(9), pals::kelly()) %>% unname()) +
cowplot::theme_cowplot() + theme(legend.position = "none") + facet_wrap(~CT)

hclust
pb <- pb <- data.table::fread('~/data/scEiaD_modeling/hs111_mature_eye_neural/hs111_mature_eye_20240924_full__neural5000hvg_200e_50l.pseudoBulk.leiden3.csv.gz')
colnames(pb) <- gsub("\\.\\d+","",colnames(pb))
hvg <- data.table::fread('~/data/scEiaD_modeling/hs111_mature_eye_neural/hvg2000.csv.gz')[-1,]
rnames <- pb$V1
clust <- str_extract(rnames, '\\d+') %>% as.integer()
pb <- pb[,-1] %>% as.matrix()
row.names(pb) <- as.character(clust)
pb <- pb[as.character(obs_neural$nlabels$leiden3),]
pb_norm <- metamoRph::normalize_data(t(pb), sample_scale = 'cpm') %>% t()
Sample CPM scaling
log1p scaling
pb_norm <- pb_norm[,hvg$V2]
#pb_norm <- pb_norm[,hvg$V2[!hvg$V2 %in% c(cc_genes,ribo_genes)]]
# https://stats.stackexchange.com/questions/31565/compute-a-cosine-dissimilarity-matrix-in-r
sim <- pb_norm / sqrt(rowSums(pb_norm * pb_norm))
sim <- sim %*% t(sim)
D_sim <- as.dist(1 - sim)
Found more than one class "dist" in cache; using the first, from namespace 'BiocGenerics'
Also defined by ‘spam’
hclust_sim <- hclust(D_sim, method = 'average')
hclust_sim$labels <- obs_neural$nlabels %>% pull(leiden3)
library(ggtree)
p <- ggtree(hclust_sim)
p$data <- p$data %>% left_join(obs_neural$nlabels, by = c("label" = "leiden3")) %>%
mutate(techRatio = round(techRatio, digits = 2))
p + layout_dendrogram() +
geom_tiplab(aes(label = paste(label, CT, studyCount, TotalCount, techRatio, sep = ' - '), color = CT)) +
theme_dendrogram(plot.margin=margin(16,16,300,16)) +
scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) +
guides(color="none")

p <- ggtree(hclust_sim)
p$data <- p$data %>% left_join(obs_neural$nlabels %>% mutate(studies = case_when(studyCount ==1 ~ studies,
TRUE ~ "multiple")), by = c("label" = "leiden3"))
p + layout_dendrogram() +
geom_tiplab(aes(label = paste(label, CT, studies, sep = ' - '), color = CT)) +
geom_tippoint(aes(shape = studies), size= 3) +
theme_dendrogram(plot.margin=margin(16,16,300,16)) +
scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) +
guides(color="none")

NA
NA
NA
NA
LS0tCnRpdGxlOiAiSHVtYW4gTWF0dXJlIEV5ZSwgTmV1cmFsIEFzc2Vzc21lbnQiCm91dHB1dDoKIGh0bWxfbm90ZWJvb2s6CiAgYXV0aG9yOiAiRGF2aWQgTWNHYXVnaGV5IgogIGRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKICB0aGVtZTogZmxhdGx5CiAgdG9jOiB0cnVlCiAgdG9jX2Zsb2F0OiB0cnVlCiAgY29kZV9mb2xkaW5nOiBzaG93Ci0tLQoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIG1lc3NhZ2UgPSBGQUxTRSwgIHdhcm5pbmcgPSBGQUxTRSwKICBjb2xsYXBzZSA9IFRSVUUsCiAgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA4LAogIGNvbW1lbnQgPSAiIz4iLAogIGRwaT0zMDAKKQpgYGAKCiMgU3RhZ2UgNAoKIyMgQmlvd3VsZjIKYGBge2Jhc2gsIGV2YWwgPSBGQUxTRX0KY2QgL2RhdGEvT0dWRkJfQkcvc2NFaWFELzIwMjRfMDJfMjgvc25ha2VvdXQvaHMxMTFfbWF0dXJlX2V5ZV9mdWxsL25ldXJhbF9jZWxscwptYW1iYSBkZWFjdGl2YXRlOyBtYW1iYSBhY3RpdmF0ZTsgYmFzaCB+L2dpdC9zY0VpYURfbW9kZWxpbmcvU25ha2VtYWtlLndyYXBwZXIuc2ggfi9naXQvc2NFaWFEX21vZGVsaW5nL3dvcmtmbG93L1NuYWtlZmlsZSB+L2dpdC9zY0VpYURfbW9kZWxpbmcvY29uZmlnL2NvbmZpZ19oczExMV9tYXR1cmVfZXllX2Z1bGxfX25ldXJhbC55YW1sIH4vZ2l0L3NjRWlhRF9tb2RlbGluZy9jb25maWcvY2x1c3Rlci5qc29uCmBgYAoKIyMgQXNzZXNzIE91dHB1dApgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCnNvdXJjZSgnYW5hbHlzaXNfc2NyaXB0cy5SJykKb2JzX25ldXJhbCA8LSBwdWxsX29icygnfi9kYXRhL3NjRWlhRF9tb2RlbGluZy9oczExMV9tYXR1cmVfZXllX25ldXJhbC9oczExMV9tYXR1cmVfZXllXzIwMjQwOTI0X2Z1bGxfX25ldXJhbDUwMDBodmdfMjAwZV81MGwub2JzLmNzdi5neicsIG1hY2hpbmVfbGFiZWwgPSAnTUNUX3NjQU5WSV9zdGVwNCcsIGRyb3BfY29sID0gRkFMU0UpCmRpZmZfbmV1cmFsIDwtIHB1bGxfZGlmZigifi9kYXRhL3NjRWlhRF9tb2RlbGluZy9oczExMV9tYXR1cmVfZXllX25ldXJhbC9oczExMV9tYXR1cmVfZXllXzIwMjQwOTI0X2Z1bGxfX25ldXJhbDUwMDBodmdfMjAwZV81MGwuZGlmZnRlc3RpbmcubGVpZGVuMy5jc3YuZ3oiKQpgYGAKIyMjIFJhdGlvIChwZXJjZW50YWdlKSBvZiBsYWJlbGxlZCBjZWxsIHR5cGVzIGZvciBlYWNoIGxlaWRlbjMgY2x1c3RlcgpgYGB7cn0Kb2JzX25ldXJhbCRsYWJlbHMgJT4lIAogIGFycmFuZ2UobUNUKSAlPiUgCiAgbXV0YXRlKGxlaWRlbjMgPSBhcy5mYWN0b3IobGVpZGVuMykpICU+JSAKICBEVDo6ZGF0YXRhYmxlKGZpbHRlciA9ICd0b3AnKQpgYGAKCiMjIyBNaXhlZCBjbHVzdGVycwpgYGB7cn0Kb2JzX25ldXJhbCRsYWJlbHMgJT4lIAogIGZpbHRlcihncmVwbCgiLCIsIG1NQ1QpKSAlPiUgCiAgYXJyYW5nZShtQ1QpICU+JSAKICBtdXRhdGUobGVpZGVuMyA9IGFzLmZhY3RvcihsZWlkZW4zKSkgJT4lIAogIERUOjpkYXRhdGFibGUoZmlsdGVyID0gJ3RvcCcpCmBgYAoKIyMgVU1BUCBQbG90cwpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEyfQpvYnNfbmV1cmFsJG9icyAlPiUgCiAgbGVmdF9qb2luKG9ic19uZXVyYWwkbGFiZWxzLCBieSA9ICdsZWlkZW4zJykgJT4lIAogIGdncGxvdChhZXMoeD11bWFwMSx5PXVtYXAyKSkgKwogIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IE1DVF9zY0FOVklfc3RlcDQpLCBwb2ludHNpemUgPSAwLjgsIGFscGhhID0gMC41KSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkoTUNUX3NjQU5WSV9zdGVwNCkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJpc2UodW1hcDEgPSBtZWRpYW4odW1hcDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW1hcDIgPSBtZWRpYW4odW1hcDIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyhsYWJlbCA9IE1DVF9zY0FOVklfc3RlcDQsIGNvbG9yID0gTUNUX3NjQU5WSV9zdGVwNCkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKb2JzX25ldXJhbCRvYnMgJT4lIAogIGxlZnRfam9pbihvYnNfbmV1cmFsJGxhYmVscywgYnkgPSAnbGVpZGVuMycpICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW1hcDEseT11bWFwMikpICsKICBzY2F0dGVybW9yZTo6Z2VvbV9zY2F0dGVybW9yZShhZXMoY29sb3IgPSBNQ1Rfc2NBTlZJKSwgcG9pbnRzaXplID0gMC44LCBhbHBoYSA9IDAuNSkgKwogIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IC4gJT4lIGdyb3VwX2J5KGxlaWRlbjMpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKHVtYXAxID0gbWVkaWFuKHVtYXAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVtYXAyID0gbWVkaWFuKHVtYXAyKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMobGFiZWwgPSBsZWlkZW4zKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKHBhbHM6OmFscGhhYmV0MigpLCBwYWxzOjpnbGFzYmV5KCkpICU+JSB1bm5hbWUoKSkgKyAKICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgoKb2JzX25ldXJhbCRvYnMgJT4lIAogIGxlZnRfam9pbihvYnNfbmV1cmFsJGxhYmVscywgYnkgPSAnbGVpZGVuMycpICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW1hcDEseT11bWFwMikpICsKICBzY2F0dGVybW9yZTo6Z2VvbV9zY2F0dGVybW9yZShhZXMoY29sb3IgPSBzdHVkeVJhdGlvKSwgcG9pbnRzaXplID0gMC43KSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKwogIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKSAKCm9ic19uZXVyYWwkb2JzICU+JSAKICBsZWZ0X2pvaW4ob2JzX25ldXJhbCRsYWJlbHMsIGJ5ID0gJ2xlaWRlbjMnKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gbU1DVCksIHBvaW50c2l6ZSA9IDAuOCwgYWxwaGEgPSAwLjUpICsKICBnZ3JlcGVsOjpnZW9tX2xhYmVsX3JlcGVsKGRhdGEgPSAuICU+JSBncm91cF9ieShtTUNUKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZSh1bWFwMSA9IG1lZGlhbih1bWFwMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bWFwMiA9IG1lZGlhbih1bWFwMikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gbU1DVCwgY29sb3IgPSBtTUNUKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKHBhbHM6OmFscGhhYmV0MigpLCBwYWxzOjpnbGFzYmV5KCkpICU+JSB1bm5hbWUoKSkgKyAKICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgoKYGBgCgojIyBoY2x1c3QKVGFrZSBwc2V1ZG9idWxrIHZhbHVlcyAoYXQgdGhlIGNsdXN0ZXIgbGV2ZWwpIGFuZCBoaWVyYXJjaGljYWxseSBjbHVzdGVyIHRoZW0gdG8gZW5zdXJlIAp0aGVyZSBhcmVuJ3QgYW55IGlzc3VlcyBpbiBlaXRoZXIgdGhlIG92ZXJhbGwgc3RydWN0dXJlIChlLmcuIHJvZCBhbmQgY29uZXMgYXJlIGludGVyc3BlcnNlKWQKYW5kL29yIHRvIGlkZW50aWZ5IGFueSBwb3RlbnRpYWwgbWlzbGFiZWxlZCBjbHVzdGVycwoKYGBge3IsIGZpZy53aWR0aCA9IDE4LCBmaWcuaGVpZ2h0ID0gMTB9CnBiIDwtIGRhdGEudGFibGU6OmZyZWFkKCd+L2RhdGEvc2NFaWFEX21vZGVsaW5nL2hzMTExX21hdHVyZV9leWVfbmV1cmFsL2hzMTExX21hdHVyZV9leWVfMjAyNDA5MjRfZnVsbF9fbmV1cmFsNTAwMGh2Z18yMDBlXzUwbC5wc2V1ZG9CdWxrLmxlaWRlbjMuY3N2Lmd6JykKY29sbmFtZXMocGIpIDwtIGdzdWIoIlxcLlxcZCsiLCIiLGNvbG5hbWVzKHBiKSkKaHZnIDwtIGRhdGEudGFibGU6OmZyZWFkKCd+L2RhdGEvc2NFaWFEX21vZGVsaW5nL2hzMTExX21hdHVyZV9leWVfbmV1cmFsL2h2ZzUwMDAuY3N2Lmd6JylbLTEsXQpybmFtZXMgPC0gcGIkVjEKY2x1c3QgPC0gc3RyX2V4dHJhY3Qocm5hbWVzLCAnXFxkKycpICU+JSBhcy5pbnRlZ2VyKCkKcGIgPC0gcGJbLC0xXSAlPiUgYXMubWF0cml4KCkKcm93Lm5hbWVzKHBiKSA8LSBhcy5jaGFyYWN0ZXIoY2x1c3QpCnBiIDwtIHBiW2FzLmNoYXJhY3RlcihvYnNfbmV1cmFsJGxhYmVscyRsZWlkZW4zKSxdCgpwYl9ub3JtIDwtIG1ldGFtb1JwaDo6bm9ybWFsaXplX2RhdGEodChwYiksIHNhbXBsZV9zY2FsZSA9ICdjcG0nKSAlPiUgdCgpIAoKIyByZW1vdmUgY2VsbCBjeWNsZSBnZW5lcwpjb252X3RhYmxlIDwtIEFubm90YXRpb25EYmk6OnNlbGVjdChvcmcuSHMuZWcuZGI6Om9yZy5Icy5lZy5kYiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleXM9Z3N1YignXFwuXFxkKycsJycsdW5pcXVlKGNvbG5hbWVzKHBiX25vcm0pKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbnM9YygiRU5TRU1CTCIsIlNZTUJPTCIsICJNQVAiLCJHRU5FTkFNRSIsICJFTlRSRVpJRCIpLCBrZXl0eXBlPSJFTlNFTUJMIikKCmNjX2dlbmVzIDwtIGh2ZyAlPiUgbXV0YXRlKEVOU0VNQkwgPSBnc3ViKCJcXC5cXGQrIiwiIixWMikpICU+JSAKICBsZWZ0X2pvaW4oY29udl90YWJsZSwgYnkgPSAiRU5TRU1CTCIpICU+JSAKICBtdXRhdGUoY2NfZ2VuZXMgPSBjYXNlX3doZW4oU1lNQk9MICVpbiUgKFNldXJhdDo6Y2MuZ2VuZXMudXBkYXRlZC4yMDE5ICU+JSB1bmxpc3QoKSkgfiBUUlVFKSkgJT4lIAogIGZpbHRlcihjY19nZW5lcykgJT4lIHB1bGwoVjIpCnJpYm9fZ2VuZXMgPC0gaHZnICU+JSBtdXRhdGUoRU5TRU1CTCA9IGdzdWIoIlxcLlxcZCsiLCIiLFYyKSkgJT4lIAogIGxlZnRfam9pbihjb252X3RhYmxlLCBieSA9ICJFTlNFTUJMIikgJT4lIGZpbHRlcihncmVwbCgiXlJQTHxeUlBTfF5NVCIsU1lNQk9MKSkgJT4lIAogIHB1bGwoU1lNQk9MKQoKcGJfbm9ybSA8LSBwYl9ub3JtWyxodmckVjJdCiNwYl9ub3JtIDwtIHBiX25vcm1bLGh2ZyRWMlshaHZnJFYyICVpbiUgYyhjY19nZW5lcyxyaWJvX2dlbmVzKV1dCiMgaHR0cHM6Ly9zdGF0cy5zdGFja2V4Y2hhbmdlLmNvbS9xdWVzdGlvbnMvMzE1NjUvY29tcHV0ZS1hLWNvc2luZS1kaXNzaW1pbGFyaXR5LW1hdHJpeC1pbi1yCnNpbSA8LSBwYl9ub3JtIC8gc3FydChyb3dTdW1zKHBiX25vcm0gKiBwYl9ub3JtKSkKc2ltIDwtIHNpbSAlKiUgdChzaW0pCkRfc2ltIDwtIGFzLmRpc3QoMSAtIHNpbSkKCmhjbHVzdF9zaW0gPC0gaGNsdXN0KERfc2ltLCBtZXRob2QgPSAnYXZlcmFnZScpCgpoY2x1c3Rfc2ltJGxhYmVscyA8LSBvYnNfbmV1cmFsJGxhYmVscyAlPiUgcHVsbChsZWlkZW4zKQoKbGlicmFyeShnZ3RyZWUpCnAgPC0gZ2d0cmVlKGhjbHVzdF9zaW0pCnAkZGF0YSA8LSBwJGRhdGEgJT4lIGxlZnRfam9pbihvYnNfbmV1cmFsJGxhYmVscywgYnkgPSBjKCJsYWJlbCIgPSAibGVpZGVuMyIpKSAlPiUgCiAgbXV0YXRlKHRlY2hSYXRpbyA9IHJvdW5kKHRlY2hSYXRpbywgZGlnaXRzID0gKSkKcCArIGxheW91dF9kZW5kcm9ncmFtKCkgKwogIGdlb21fdGlwbGFiKGFlcyhsYWJlbCA9IHBhc3RlKGxhYmVsLCBtTUNULCBzdHVkeUNvdW50LCBUb3RhbENvdW50LCB0ZWNoUmF0aW8sIHNlcCA9ICcgLSAnKSwgY29sb3IgPSBtQ1QpKSArIAogIHRoZW1lX2RlbmRyb2dyYW0ocGxvdC5tYXJnaW49bWFyZ2luKDE2LDE2LDMwMCwxNikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgZ3VpZGVzKGNvbG9yPSJub25lIikKCgpwIDwtIGdndHJlZShoY2x1c3Rfc2ltKQpwJGRhdGEgPC0gcCRkYXRhICU+JSBsZWZ0X2pvaW4ob2JzX25ldXJhbCRsYWJlbHMgJT4lIG11dGF0ZShzdHVkaWVzID0gY2FzZV93aGVuKHN0dWR5Q291bnQgPT0xIH4gc3R1ZGllcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gIm11bHRpcGxlIikpLCBieSA9IGMoImxhYmVsIiA9ICJsZWlkZW4zIikpIAoKcCArIGxheW91dF9kZW5kcm9ncmFtKCkgKwogIGdlb21fdGlwbGFiKGFlcyhsYWJlbCA9IHBhc3RlKGxhYmVsLCBtTUNULCBzdHVkaWVzLCBzZXAgPSAnIC0gJyksIGNvbG9yID0gbUNUKSkgKyAKICBnZW9tX3RpcHBvaW50KGFlcyhzaGFwZSA9IHN0dWRpZXMpLCBzaXplPSAzKSArCiAgdGhlbWVfZGVuZHJvZ3JhbShwbG90Lm1hcmdpbj1tYXJnaW4oMTYsMTYsMzAwLDE2KSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKHBhbHM6OmFscGhhYmV0MigpLCBwYWxzOjpnbGFzYmV5KCkpICU+JSB1bm5hbWUoKSkgKyAKICBndWlkZXMoY29sb3I9Im5vbmUiKQoKCgpwIDwtIGdndHJlZShoY2x1c3Rfc2ltKQpwJGRhdGEgPC0gcCRkYXRhICU+JSBsZWZ0X2pvaW4ob2JzX25ldXJhbCRsYWJlbHMgJT4lIG11dGF0ZShzdHVkaWVzID0gY2FzZV93aGVuKHN0dWR5UmF0aW8gPT0xIH4gc3R1ZGllc1JhdGlvLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAibXVsdGlwbGUiKSksIGJ5ID0gYygibGFiZWwiID0gImxlaWRlbjMiKSkgCgpwICsgbGF5b3V0X2RlbmRyb2dyYW0oKSArCiAgZ2VvbV90aXBsYWIoYWVzKGxhYmVsID0gcGFzdGUobGFiZWwsIG1NQ1QsIHN0dWRpZXMsIHNlcCA9ICcgLSAnKSwgY29sb3IgPSBtQ1QpKSArIAogIGdlb21fdGlwcG9pbnQoYWVzKHNoYXBlID0gc3R1ZGllcyksIHNpemU9IDMpICsKICB0aGVtZV9kZW5kcm9ncmFtKHBsb3QubWFyZ2luPW1hcmdpbigxNiwxNiwzMDAsMTYpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSkgJT4lIHVubmFtZSgpKSArIAogIGd1aWRlcyhjb2xvcj0ibm9uZSIpCgoKYGBgCgojIyBzdXNwaWNpb3VzIGNsdXN0ZXJzCgpyZWFzb25zOgoKLSAxLiBzdHVkeSBzcGVjaWZpYyBjbHVzdGVycyBpbiAid3JvbmciIChub3Qgd2l0aCBsaWtlKSBwYXJ0cyBvZiB0aGUgdHJlZQotIDIuIG1peGVkIGNlbGwgdHlwZXMKCmBgYHtyfQpzdXNfbmV1cmFsIDwtICNjKDcxLDkzLDEyNiwxMTIsMTQ0LDM5LDIxLDg1LDEzNSw3LDc0LDM2LDEyNCkKICBjKAogICAgIyByZWFzb24gMQogICAgYyg5OSwgOTcsIDM4LCA1MCw0MCwgOTgsIDk0KSwKICAgICMgcmVhc29uIDIKICAgIGMoODUsIDk1LCA5MSwgNTIsIDc3KQogICkKIyB0byBwcm92aWRlIGFuIGFkZGl0aW9uYWwgbGF5ZXIgb2YgcmVzb2x1dGlvbiB0byB0aGUgY2VsbCB0eXBlCmhyX25ldXJhbCA8LSBsaXN0KCkKYGBgCgoKIyMjIFVNQVAgUGxvdHMKYGBge3IsIGZpZy53aWR0aD0xNiwgZmlnLmhlaWdodD0xNn0Kb2JzX25ldXJhbCRvYnMgJT4lIAogIGxlZnRfam9pbihvYnNfbmV1cmFsJGxhYmVscywgYnkgPSAnbGVpZGVuMycpICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW1hcDEseT11bWFwMikpICsKICBzY2F0dGVybW9yZTo6Z2VvbV9zY2F0dGVybW9yZShhZXMoY29sb3IgPSBNQ1Rfc2NBTlZJX3N0ZXA0KSwgcG9pbnRzaXplID0gMC44LCBhbHBoYSA9IDAuNSkgKwogIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGRhdGEgPSBvYnNfbmV1cmFsJG9icyAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZWZ0X2pvaW4ob2JzX25ldXJhbCRsYWJlbHMsIGJ5ID0gJ2xlaWRlbjMnKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIobGVpZGVuMyAlaW4lIHN1c19uZXVyYWwpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gJ3JlZCcsIHBvaW50c2l6ZSA9IDAuOCwgYWxwaGEgPSAwLjUpICsKICBnZ3JlcGVsOjpnZW9tX2xhYmVsX3JlcGVsKGRhdGEgPSAuICU+JSBncm91cF9ieShsZWlkZW4zKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihsZWlkZW4zICVpbiUgc3VzX25ldXJhbCkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJpc2UodW1hcDEgPSBtZWRpYW4odW1hcDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW1hcDIgPSBtZWRpYW4odW1hcDIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyhsYWJlbCA9IGxlaWRlbjMpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSkgJT4lIHVubmFtZSgpKSArIAogIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKQoKCm9ic19uZXVyYWwkb2JzICU+JSAKICBsZWZ0X2pvaW4ob2JzX25ldXJhbCRsYWJlbHMsIGJ5ID0gJ2xlaWRlbjMnKSAlPiUgCiAgZmlsdGVyKCFsZWlkZW4zICVpbiUgc3VzX25ldXJhbCkgJT4lIAogIGdncGxvdChhZXMoeD11bWFwMSx5PXVtYXAyKSkgKwogIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IG1DVCksIHBvaW50c2l6ZSA9IDAuOCwgYWxwaGEgPSAwLjUpICsKICBnZ3JlcGVsOjpnZW9tX2xhYmVsX3JlcGVsKGRhdGEgPSAuICU+JSBncm91cF9ieShsZWlkZW4zKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihsZWlkZW4zICVpbiUgc3VzX25ldXJhbCkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJpc2UodW1hcDEgPSBtZWRpYW4odW1hcDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW1hcDIgPSBtZWRpYW4odW1hcDIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyhsYWJlbCA9IGxlaWRlbjMpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSkgJT4lIHVubmFtZSgpKSArIAogIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKQpgYGAKCiMgQ2FsbCBDVAoKYGBge3J9CmhyX25ldXJhbCA8LSBsaXN0KCkKYGBgCgojIyBwaG90b3JlY2VwdG9yCgoqKlJlbW92aW5nIHR3byBtb3JlIGNsdXN0ZXJzIHdpdGggbm8gQVJSMyAvIFJITyBzaWduYWwgKGFuZCBzdXNwaWNpb3VzIGhjbHVzdCkgYWJvdmUqKgoKaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9zNDE1OTgtMDIwLTY2MDkyLTkKYGBge3J9Cgp0aWIgPC0gZGlmZl9uZXVyYWwkZGlmZl90ZXN0aW5nICU+JSAKICBsZWZ0X2pvaW4ob2JzX25ldXJhbCRsYWJlbHMsIGJ5ID0gYygnYmFzZSc9J2xlaWRlbjMnKSkgJT4lIAogIGZpbHRlcihtQ1QgJWluJSBjKCdyb2QnLCdjb25lJyksICFiYXNlICVpbiUgc3VzX25ldXJhbCkgJT4lIAogIGxlZnRfam9pbihjb252X3RhYmxlKSAlPiUgCiAgZmlsdGVyKFNZTUJPTCAlaW4lIGMoJ0FSUjMnLCdPUE4xTFcnLCdPUE4xU1cnLCdSSE8nLCAnT1BOMU1XJywgJ1JDVlJOJywiQ1JYIiwiUFJPTTEiKSkgJT4lIAogIHNlbGVjdChTWU1CT0wsIGJhc2UsIGxvZ2ZvbGRjaGFuZ2VzKSAlPiUgCiAgcGl2b3Rfd2lkZXIodmFsdWVzX2Zyb20gPSBsb2dmb2xkY2hhbmdlcywgbmFtZXNfZnJvbSA9IGJhc2UpCgptYXQgPC0gdGliICU+JSBzZWxlY3QoLTEpICU+JSBhcy5tYXRyaXgoKQpyb3cubmFtZXMobWF0KSA8LSB0aWIgJT4lIHB1bGwoMSkKCmNvbF9mdW4gPSBjaXJjbGl6ZTo6Y29sb3JSYW1wMihjKC01LCAwLCA1KSwgYygiYmx1ZSIsICJ3aGl0ZSIsICJyZWQiKSkKQ29tcGxleEhlYXRtYXA6OkhlYXRtYXAobWF0LCBjb2w9Y29sX2Z1bikKCnN1c19uZXVyYWwgPC0gYyhzdXNfbmV1cmFsLAogICAgICAgICAgICAgICAgICAgICAgICAgICBjKDg2LDI5KSkKCmhyX25ldXJhbCRgY29uZSAocylgIDwtIGMoODcpCmhyX25ldXJhbCRgY29uZSAobWwpYCA8LSBjKDcyLDc0LDIwLDkwLDgxKQpgYGAKCmBgYHtyIGVjaG89RkFMU0UsIGZpZy5jYXA9InByIiwgb3V0LndpZHRoID0gJzIwJSd9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvcHIucG5nIikKIyBodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTU5OC0wMjAtNjYwOTItOQpgYGAKCgpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9OX0Kb2JzX25ldXJhbCRvYnMgJT4lIAogIGxlZnRfam9pbihvYnNfbmV1cmFsJGxhYmVscywgYnkgPSAnbGVpZGVuMycpICU+JSAKICBmaWx0ZXIoIWxlaWRlbjMgJWluJSBzdXNfbmV1cmFsLAogICAgICAgICBtQ1QgJWluJSBjKCJyb2QiLCAiY29uZSIpKSAlPiUgCiAgbGVmdF9qb2luKGhyX25ldXJhbCAlPiUgZW5mcmFtZShuYW1lID0gJ0NlbGwgVHlwZScsIHZhbHVlID0gJ2xlaWRlbjMnKSAlPiUgdW5uZXN0KGxlaWRlbjMpLAogICAgICAgICAgICAgYnkgPSAnbGVpZGVuMycpICU+JSAKICBtdXRhdGUoYENlbGwgVHlwZWAgPSBjYXNlX3doZW4oaXMubmEoYENlbGwgVHlwZWApIH4gbUNULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gYENlbGwgVHlwZWApKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gYENlbGwgVHlwZWApLCBwb2ludHNpemUgPSAwLjgsIGFscGhhID0gMC41KSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkobGVpZGVuMykgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJpc2UodW1hcDEgPSBtZWRpYW4odW1hcDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW1hcDIgPSBtZWRpYW4odW1hcDIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyhsYWJlbCA9IGxlaWRlbjMpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IAogICAgICAgICAgICAgICAgICAgICAgIGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSwgcGFsczo6YWxwaGFiZXQoKSwgcGFsczo6YnJld2VyLnNldDEobj0xMikpICU+JSB1bm5hbWUoKSkgKyAKICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkKYGBgCgoKIyMgYmlwb2xhcgoKYGBge3IgZWNobz1GQUxTRSwgZmlnLmNhcD0iYmlwb2xhciIsIG91dC53aWR0aCA9ICcyMCUnfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL2JpcG9sYXIucG5nIikKIyBodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTU5OC0wMjAtNjYwOTItOQpgYGAKCgoKYGBge3J9CnRpYiA8LSBkaWZmX25ldXJhbCRkaWZmX3Rlc3RpbmcgJT4lIAogIGxlZnRfam9pbihvYnNfbmV1cmFsJGxhYmVscywgYnkgPSBjKCdiYXNlJz0nbGVpZGVuMycpKSAlPiUgCiAgZmlsdGVyKG1DVCAlaW4lIGMoJ2JpcG9sYXInLCdyb2QgYmlwb2xhcicpLCAhYmFzZSAlaW4lIHN1c19uZXVyYWwpICU+JSAKICBsZWZ0X2pvaW4oY29udl90YWJsZSkgJT4lIAogIGZpbHRlcihTWU1CT0wgJWluJSBjKCdQUktDQScsJ0dSTTYnLCdHUklLMScsJ05JRjNMMScsCiAgICAgICAgICAgICAgICAgICAgICAgJ0xJTkMwMDQ3MCcsJ0RPSzUnLCdORUxMMicsJ1NUWDE4JywKICAgICAgICAgICAgICAgICAgICAgICAnT0RGMkwnLCdGQU0xOUE0JywnTUVJUzInLCdDQUxCMScsICdGVVQ0JywKICAgICAgICAgICAgICAgICAgICAgICAnU0NHMicsJ0xSUFBSQycsJ0ZFWkYxJykpICU+JSAKICBzZWxlY3QoU1lNQk9MLCBiYXNlLCBsb2dmb2xkY2hhbmdlcykgJT4lIAogIHBpdm90X3dpZGVyKHZhbHVlc19mcm9tID0gbG9nZm9sZGNoYW5nZXMsIG5hbWVzX2Zyb20gPSBiYXNlKQoKbWF0IDwtIHRpYiAlPiUgc2VsZWN0KC0xKSAlPiUgYXMubWF0cml4KCkKcm93Lm5hbWVzKG1hdCkgPC0gdGliICU+JSBwdWxsKDEpCgpjb2xfZnVuID0gY2lyY2xpemU6OmNvbG9yUmFtcDIoYygtNSwgMCwgNSksIGMoImJsdWUiLCAid2hpdGUiLCAicmVkIikpCkNvbXBsZXhIZWF0bWFwOjpIZWF0bWFwKG1hdCwgY29sPWNvbF9mdW4pCgpocl9uZXVyYWwkYGJpcG9sYXIgKHJvZClgIDwtIGMoMTIsNzgsMzIpCmhyX25ldXJhbCRgYmlwb2xhciAob2ZmKWAgPC0gYygxMSwyOCwzMywzNiw0NiwxNykKaHJfbmV1cmFsJGBiaXBvbGFyIChvbilgIDwtIGMoNjgsNTUsNiw4MiwyNywzMCwzOSkKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9OX0Kb2JzX25ldXJhbCRvYnMgJT4lIAogIGxlZnRfam9pbihvYnNfbmV1cmFsJGxhYmVscywgYnkgPSAnbGVpZGVuMycpICU+JSAKICBmaWx0ZXIoIWxlaWRlbjMgJWluJSBzdXNfbmV1cmFsLAogICAgICAgICBtQ1QgJWluJSBjKCJiaXBvbGFyIiwgInJvZCBiaXBvbGFyIikpICU+JSAKICBsZWZ0X2pvaW4oaHJfbmV1cmFsICU+JSBlbmZyYW1lKG5hbWUgPSAnQ2VsbCBUeXBlJywgdmFsdWUgPSAnbGVpZGVuMycpICU+JSB1bm5lc3QobGVpZGVuMyksCiAgICAgICAgICAgICBieSA9ICdsZWlkZW4zJykgJT4lIAogIGdncGxvdChhZXMoeD11bWFwMSx5PXVtYXAyKSkgKwogIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IGBDZWxsIFR5cGVgKSwgcG9pbnRzaXplID0gMC44LCBhbHBoYSA9IDAuNSkgKwogIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IC4gJT4lIGdyb3VwX2J5KGxlaWRlbjMpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKHVtYXAxID0gbWVkaWFuKHVtYXAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVtYXAyID0gbWVkaWFuKHVtYXAyKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMobGFiZWwgPSBsZWlkZW4zKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSAKICAgICAgICAgICAgICAgICAgICAgICBjKHBhbHM6OmFscGhhYmV0MigpLCBwYWxzOjpnbGFzYmV5KCksCiAgICAgICAgICAgICAgICAgICAgICAgICBwYWxzOjphbHBoYWJldCgpLCBwYWxzOjpicmV3ZXIuc2V0MShuPTEyKSkgJT4lIHVubmFtZSgpKSArIAogIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKQpgYGAKCiMjIGhvcml6b250YWwKCmBgYHtyIGVjaG89RkFMU0UsIGZpZy5jYXA9ImJpcG9sYXIiLCBvdXQud2lkdGggPSAnMjAlJ30Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9ob3Jpem9udGFsLnBuZyIpCiMgaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9zNDE1OTgtMDIwLTY2MDkyLTkKYGBgCgpgYGB7cn0KZGlmZl9uZXVyYWwkZGlmZl90ZXN0aW5nICU+JSAKICBsZWZ0X2pvaW4ob2JzX25ldXJhbCRsYWJlbHMsIGJ5ID0gYygnYmFzZSc9J2xlaWRlbjMnKSkgJT4lIAogIGZpbHRlcihtQ1QgPT0gJ2hvcml6b250YWwnKSAlPiUgCiAgbGVmdF9qb2luKGNvbnZfdGFibGUpICU+JSAKICBmaWx0ZXIoU1lNQk9MICVpbiUgYygnT05FQ1VUMScsJ09ORUNVVDInLCdMSFgxJywnSVNMMScpKSAlPiUgCiAgc2VsZWN0KFNZTUJPTCwgYmFzZSwgbG9nZm9sZGNoYW5nZXMpICU+JSAKICBnZ3Bsb3QoYWVzKHg9YXMuZmFjdG9yKGJhc2UpLCB5PVNZTUJPTCwgY29sb3IgPSBsb2dmb2xkY2hhbmdlcykpICsgCiAgZ2VvbV9wb2ludChzaXplID0xMCkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpCgoKdGliIDwtIGRpZmZfbmV1cmFsJGRpZmZfdGVzdGluZyAlPiUgCiAgbGVmdF9qb2luKG9ic19uZXVyYWwkbGFiZWxzLCBieSA9IGMoJ2Jhc2UnPSdsZWlkZW4zJykpICU+JSAKICBmaWx0ZXIobUNUID09ICdob3Jpem9udGFsJykgJT4lIAogIGxlZnRfam9pbihjb252X3RhYmxlKSAlPiUgCiAgZmlsdGVyKFNZTUJPTCAlaW4lIGMoJ09ORUNVVDEnLCdPTkVDVVQyJywnTEhYMScsJ0lTTDEnKSkgJT4lIAogIHNlbGVjdChTWU1CT0wsIGJhc2UsIGxvZ2ZvbGRjaGFuZ2VzKSAlPiUgCiAgcGl2b3Rfd2lkZXIodmFsdWVzX2Zyb20gPSBsb2dmb2xkY2hhbmdlcywgbmFtZXNfZnJvbSA9IGJhc2UpCgptYXQgPC0gdGliICU+JSBzZWxlY3QoLTEpICU+JSBhcy5tYXRyaXgoKQpyb3cubmFtZXMobWF0KSA8LSB0aWIgJT4lIHB1bGwoMSkKY29sX2Z1biA9IGNpcmNsaXplOjpjb2xvclJhbXAyKGMoLTQsIDAsIDQpLCBjKCJibHVlIiwgIndoaXRlIiwgInJlZCIpKQpDb21wbGV4SGVhdG1hcDo6SGVhdG1hcChtYXQsIGNvbD1jb2xfZnVuKQoKaHJfbmV1cmFsJGBob3Jpem9udGFsIChoMSlgIDwtIGMoMTgsNzApCmhyX25ldXJhbCRgaG9yaXpvbnRhbCAoaDIpYCA8LSBjKDU4KQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD05fQpvYnNfbmV1cmFsJG9icyAlPiUgCiAgbGVmdF9qb2luKG9ic19uZXVyYWwkbGFiZWxzLCBieSA9ICdsZWlkZW4zJykgJT4lIAogIGZpbHRlcighbGVpZGVuMyAlaW4lIHN1c19uZXVyYWwsCiAgICAgICAgIG1DVCAlaW4lIGMoImhvcml6b250YWwiKSkgJT4lIAogIGxlZnRfam9pbihocl9uZXVyYWwgJT4lIGVuZnJhbWUobmFtZSA9ICdDZWxsIFR5cGUnLCB2YWx1ZSA9ICdsZWlkZW4zJykgJT4lIHVubmVzdChsZWlkZW4zKSwKICAgICAgICAgICAgIGJ5ID0gJ2xlaWRlbjMnKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gYENlbGwgVHlwZWApLCBwb2ludHNpemUgPSAwLjgsIGFscGhhID0gMC41KSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkobGVpZGVuMykgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJpc2UodW1hcDEgPSBtZWRpYW4odW1hcDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW1hcDIgPSBtZWRpYW4odW1hcDIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyhsYWJlbCA9IGxlaWRlbjMpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IAogICAgICAgICAgICAgICAgICAgICAgIGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSwKICAgICAgICAgICAgICAgICAgICAgICAgIHBhbHM6OmFscGhhYmV0KCksIHBhbHM6OmJyZXdlci5zZXQxKG49MTIpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpCmBgYAoKCiMjIGFtYWNyaW5lCk1vc3QgYW1hY3JpbmUgY2VsbHMgYXJlIGluaGliaXRvcnkgbmV1cm9ucyB1dGlsaXppbmcgR0FCQSBvciBnbHljaW5lIGFzIG5ldXJvdHJhbnNtaXR0ZXJzLiBCeSBhc3Nlc3NpbmcgdGhlIGV4cHJlc3Npb24gb2YgbWFya2VyIGdlbmVzIGZvciBHQUJBZXJnaWMgKGdsdXRhbWF0ZSBjYXJib3h5bGFzZSwgR0FEMSBhbmQgR0FEMikgYW5kIGdseWNpbmVyZ2ljIChTTEM2QTksIGVuY29kaW5nIHRoZSBoaWdoIGFmZmluaXR5IGdseWNpbmUgdHJhbnNwb3J0ZXIgR0xZVDEpIGFtYWNyaW5lczIwLCB3ZSBpZGVudGlmaWVkIDE2IHB1dGF0aXZlIEdBQkFlcmdpYyBhbmQgOCBwdXRhdGl2ZSBnbHljaW5lcmdpYyBhbWFjcmluZSBjZWxsIHR5cGVzIGFtb25nIGEgdG90YWwgb2YgMjUgdHlwZXMgKEZpZy4gM2EsYikuIE9uZSB0eXBlIChDMTQpIGV4cHJlc3NlZCBub25lIG9mIHRoZXNlIHRocmVlIGdlbmVzIGF0IGhpZ2ggbGV2ZWxzLCBhbmQgbWlnaHQgY29ycmVzcG9uZCB0byBhIG5vbi1HQUJBZXJnaWMgbm9uLUdseWNpbmVyZ2ljIChuR25HKSB0eXBlIGlkZW50aWZpZWQgaW4gbW91c2UyMTIyIC4gT25lIG9mIHRoZSBnbHljaW5lcmdpYyB0eXBlcyAoQzE3KSBhbHNvIGV4cHJlc3NlZCBHQUQyLCByYWlzaW5nIHRoZSBwb3NzaWJpbGl0eSB0aGF0IGl0IHVzZXMgYm90aCB0cmFuc21pdHRlcnMuCgoqKlJFTU9WSU5HIENMVVNURVIgMCBBUyBJVCBJUyBTVFVEWSBTUEVDSUZJQyBBTkQgTk9UIEVYUFJFU1NJTkcgQU5ZIEtOT1dOIEdBQkEvR0xZQ0kgTUFSS0VSUyoqCgpgYGB7ciwgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9Mn0KCgp0aWIgPC0gZGlmZl9uZXVyYWwkZGlmZl90ZXN0aW5nICU+JSAKICBsZWZ0X2pvaW4ob2JzX25ldXJhbCRsYWJlbHMsIGJ5ID0gYygnYmFzZSc9J2xlaWRlbjMnKSkgJT4lIAogIGZpbHRlcihtQ1QgPT0gJ2FtYWNyaW5lJykgJT4lIAogIGxlZnRfam9pbihjb252X3RhYmxlKSAlPiUgCiAgZmlsdGVyKFNZTUJPTCAlaW4lIGMoJ0dBRDEnLCdHQUQyJywnU0xDNkE5JywnTkZJQScpKSAlPiUgCiAgc2VsZWN0KFNZTUJPTCwgYmFzZSwgbG9nZm9sZGNoYW5nZXMpICU+JSAKICBwaXZvdF93aWRlcih2YWx1ZXNfZnJvbSA9IGxvZ2ZvbGRjaGFuZ2VzLCBuYW1lc19mcm9tID0gYmFzZSkKCm1hdCA8LSB0aWIgJT4lIHNlbGVjdCgtMSkgJT4lIGFzLm1hdHJpeCgpCnJvdy5uYW1lcyhtYXQpIDwtIHRpYiAlPiUgcHVsbCgxKQpjb2xfZnVuID0gY2lyY2xpemU6OmNvbG9yUmFtcDIoYygtNSwgMCwgNSksIGMoImJsdWUiLCAid2hpdGUiLCAicmVkIikpCkNvbXBsZXhIZWF0bWFwOjpIZWF0bWFwKG1hdCwgY29sPWNvbF9mdW4pCgpzdXNfbmV1cmFsIDwtIGMoc3VzX25ldXJhbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgYygwKSkKaHJfbmV1cmFsJGBhbWFjcmluZSAoZ2FiYW5lcmdpYylgIDwtIGMoNzYsNjcsNTYsNTksMzcsNjIsNzMsMTUsNjksNDQsNzUsODMsMjQsNjYsMjIsMjYsODAsNDEsNjEsOTEsNDIpCmhyX25ldXJhbCRgYW1hY3JpbmUgKGdseWNpbmVyZ2ljKWAgPC0gYygyNSwxNiw4OCw0MykKaHJfbmV1cmFsJGBhbWFjcmluZSAoZ2FiYS9nbHljaSlgIDwtIGMoOTEsMzEsNjUpCmBgYAoKYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTl9Cm9ic19uZXVyYWwkb2JzICU+JSAKICBsZWZ0X2pvaW4ob2JzX25ldXJhbCRsYWJlbHMsIGJ5ID0gJ2xlaWRlbjMnKSAlPiUgCiAgZmlsdGVyKCFsZWlkZW4zICVpbiUgc3VzX25ldXJhbCwKICAgICAgICAgbUNUICVpbiUgYygiYW1hY3JpbmUiKSkgJT4lIAogIGxlZnRfam9pbihocl9uZXVyYWwgJT4lIGVuZnJhbWUobmFtZSA9ICdDZWxsIFR5cGUnLCB2YWx1ZSA9ICdsZWlkZW4zJykgJT4lIHVubmVzdChsZWlkZW4zKSwKICAgICAgICAgICAgIGJ5ID0gJ2xlaWRlbjMnKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gYENlbGwgVHlwZWApLCBwb2ludHNpemUgPSAwLjgsIGFscGhhID0gMC41KSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkobGVpZGVuMykgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJpc2UodW1hcDEgPSBtZWRpYW4odW1hcDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW1hcDIgPSBtZWRpYW4odW1hcDIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyhsYWJlbCA9IGxlaWRlbjMpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IAogICAgICAgICAgICAgICAgICAgICAgIGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSwKICAgICAgICAgICAgICAgICAgICAgICAgIHBhbHM6OmFscGhhYmV0KCksIHBhbHM6OmJyZXdlci5zZXQxKG49MTIpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpCmBgYAoKCiMjIHJldGluYWwgZ2FuZ2xpb24KClRoZXNlIGRvbid0IG1hdGNoIHVwIHRvIHRoZSBwdWJsaXNoZWQgbWFya2VycyB2ZXJ5IHdlbGwgLSBmb3IgZXhhbXBsZSB0aGUgcGFyYXNvbCBSRyBzZWVtIHRvIGJlIGEgbWl4ZWQgT04vT0ZGIGNsdXN0ZXIuIFNob3VsZCBydW4gYSByZXRpbmFsIGdhbmdsaW9uIHNwZWNpZmljIHNjVkkgcnVuIGFuZCBzZWUgd2hldGhlciB0aGF0IGRvZXMgYSBiZXR0ZXIgam9iLiAKCmBgYHtyfQoKdGliIDwtIGRpZmZfbmV1cmFsJGRpZmZfdGVzdGluZyAlPiUgCiAgbGVmdF9qb2luKG9ic19uZXVyYWwkbGFiZWxzLCBieSA9IGMoJ2Jhc2UnPSdsZWlkZW4zJykpICU+JSAKICBmaWx0ZXIobUNUICVpbiUgYygncmV0aW5hbCBnYW5nbGlvbicpLCAhYmFzZSAlaW4lIHN1c19uZXVyYWwpICU+JSAKICBsZWZ0X2pvaW4oY29udl90YWJsZSkgJT4lIAogIGZpbHRlcihTWU1CT0wgJWluJSBjKCdUUEJHJywnVEJSMScsJ0ZBQlA0JywnQ0hSTkEyJywgJ0xNTzInLAogICdFT01FUycsJ1NTVFIyJywnRk9YUDInLCdGT1hQMScsJ1BSUjM1JywnQ0FSVFBUJywKICAnQ0RLTjJBJywnQVJQUDIxJywnT1BONCcsICdORUZNJywKICAnVFVCQjMnKSkgJT4lIAogIHNlbGVjdChTWU1CT0wsIGJhc2UsIHNjb3JlcykgJT4lIAogIHBpdm90X3dpZGVyKHZhbHVlc19mcm9tID0gc2NvcmVzLCBuYW1lc19mcm9tID0gYmFzZSkKCm1hdCA8LSB0aWIgJT4lIHNlbGVjdCgtMSkgJT4lIGFzLm1hdHJpeCgpCnJvdy5uYW1lcyhtYXQpIDwtIHRpYiAlPiUgcHVsbCgxKQoKY29sX2Z1biA9IGNpcmNsaXplOjpjb2xvclJhbXAyKGMoLTE1LCAwLCAxNSksIGMoImJsdWUiLCAid2hpdGUiLCAicmVkIikpCkNvbXBsZXhIZWF0bWFwOjpIZWF0bWFwKG1hdCwgY29sPWNvbF9mdW4pCgpgYGAKYGBge3J9Cm9ic19uZXVyYWwkb2JzICU+JSAKICBsZWZ0X2pvaW4ob2JzX25ldXJhbCRsYWJlbHMsIGJ5ID0gJ2xlaWRlbjMnKSAlPiUgCiAgZmlsdGVyKG1DVCA9PSAncmV0aW5hbCBnYW5nbGlvbicsIFN1YkNlbGxUeXBlICE9ICcnKSAlPiUgCiAgbGVmdF9qb2luKG9ic19uZXVyYWwkbGFiZWxzKSAlPiUgCiAgZ3JvdXBfYnkobGVpZGVuMywgU3ViQ2VsbFR5cGUpICU+JSAKICBzdW1tYXJpc2UoQ291bnQgPSBuKCkpICU+JSAKICBtdXRhdGUoUmF0aW8gPSBDb3VudC9zdW0oQ291bnQpKSAlPiUgCiAgZmlsdGVyKFJhdGlvID4gMC41KSAlPiUgYXJyYW5nZShTdWJDZWxsVHlwZSkKCmhyX25ldXJhbCRgcmV0aW5hbCBnYW5nbGlvbiAoT04gbWlkZ2V0KWAgPC0gYyg0NSwgNTQsNjMsIDkyKQpocl9uZXVyYWwkYHJldGluYWwgZ2FuZ2xpb24gKE9GRiBtaWRnZXQpYCA8LSBjKDE5LDM0LDU3LDcxLDc5LCA4OSwgOTYpCgpocl9uZXVyYWwkYHJldGluYWwgZ2FuZ2xpb24gKHBhcmFzb2wpYCA8LSBjKDY0KQpgYGAKYGBge3IgZWNobz1GQUxTRSwgZmlnLmNhcD0icHIiLCBvdXQud2lkdGggPSAnMjAlJ30Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9yZ2MucG5nIikKIyBodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTU5OC0wMjAtNjYwOTItOQpgYGAKCgpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9OX0Kb2JzX25ldXJhbCRvYnMgJT4lIAogIGxlZnRfam9pbihvYnNfbmV1cmFsJGxhYmVscywgYnkgPSAnbGVpZGVuMycpICU+JSAKICBmaWx0ZXIoIWxlaWRlbjMgJWluJSBzdXNfbmV1cmFsLAogICAgICAgICBtQ1QgJWluJSBjKCJyZXRpbmFsIGdhbmdsaW9uIikpICU+JSAKICBsZWZ0X2pvaW4oaHJfbmV1cmFsICU+JSBlbmZyYW1lKG5hbWUgPSAnQ2VsbCBUeXBlJywgdmFsdWUgPSAnbGVpZGVuMycpICU+JSB1bm5lc3QobGVpZGVuMyksCiAgICAgICAgICAgICBieSA9ICdsZWlkZW4zJykgJT4lIAogIG11dGF0ZShgQ2VsbCBUeXBlYCA9IGNhc2Vfd2hlbihpcy5uYShgQ2VsbCBUeXBlYCkgfiBtQ1QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiBgQ2VsbCBUeXBlYCkpICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW1hcDEseT11bWFwMikpICsKICBzY2F0dGVybW9yZTo6Z2VvbV9zY2F0dGVybW9yZShhZXMoY29sb3IgPSBgQ2VsbCBUeXBlYCksIHBvaW50c2l6ZSA9IDAuOCwgYWxwaGEgPSAxKSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkobGVpZGVuMykgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJpc2UodW1hcDEgPSBtZWRpYW4odW1hcDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW1hcDIgPSBtZWRpYW4odW1hcDIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyhsYWJlbCA9IGxlaWRlbjMpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IAogICAgICAgICAgICAgICAgICAgICAgIGMocGFsczo6Z2xhc2JleSgpLCBwYWxzOjpnbGFzYmV5KCksIHBhbHM6OmFscGhhYmV0KCksIHBhbHM6OmJyZXdlci5zZXQxKG49MTIpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpCmBgYAoKCgoKIyBORVcgCgpVcGRhdGUgb3ZlcmFsbCBncmFwaGljcyBvbiB0aGUgbmV3IGxhYmVscwoKIyMgVU1BUApgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTl9Cgpocl9sb25nIDwtIGhyX25ldXJhbCAlPiUgZW5mcmFtZShuYW1lID0gJ2hyQ1QnLCB2YWx1ZSA9ICdsZWlkZW4zJykgJT4lIHVubmVzdChsZWlkZW4zKSAKCm9ic19uZXVyYWwkbm9icyA8LSBvYnNfbmV1cmFsJG9icyAlPiUgCiAgbGVmdF9qb2luKG9ic19uZXVyYWwkbGFiZWxzLCBieSA9ICdsZWlkZW4zJykgJT4lIAogIGxlZnRfam9pbihocl9sb25nLCAKICAgICAgICAgICAgYnkgPSAnbGVpZGVuMycpICU+JQogIGZpbHRlcighbGVpZGVuMyAlaW4lIChzdXNfbmV1cmFsKSkgJT4lIAogIG11dGF0ZShDVCA9IG1DVCwKICAgICAgICAgaHJDVCA9IGNhc2Vfd2hlbighaXMubmEoaHJDVCkgfiBockNULAogICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiBDVCkpCgpvYnNfbmV1cmFsJG5sYWJlbHMgPC0gb2JzX25ldXJhbCRsYWJlbHMgJT4lIAogIGxlZnRfam9pbihocl9sb25nLCAKICAgICAgICAgICAgYnkgPSAnbGVpZGVuMycpICU+JQogIGZpbHRlcighbGVpZGVuMyAlaW4lIChzdXNfbmV1cmFsKSkgJT4lIAogIG11dGF0ZShDVCA9IG1DVCwKICAgICAgICAgaHJDVCA9IGNhc2Vfd2hlbighaXMubmEoaHJDVCkgfiBockNULAogICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiBDVCkpCgpvYnNfbmV1cmFsJG5vYnMgJT4lIAogIGdncGxvdChhZXMoeD11bWFwMSx5PXVtYXAyKSkgKwogIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IENUKSwgcG9pbnRzaXplID0gMC44LCBhbHBoYSA9IDAuNSkgKwogIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IC4gJT4lIGdyb3VwX2J5KENUKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZSh1bWFwMSA9IG1lZGlhbih1bWFwMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bWFwMiA9IG1lZGlhbih1bWFwMikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gQ1QsIGNvbG9yID0gQ1QpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSkgJT4lIHVubmFtZSgpKSArIAogIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCm9ic19uZXVyYWwkbm9icyAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gaHJDVCksIHBvaW50c2l6ZSA9IDAuOCwgYWxwaGEgPSAwLjUpICsKICBnZ3JlcGVsOjpnZW9tX2xhYmVsX3JlcGVsKGRhdGEgPSAuICU+JSBncm91cF9ieShockNUKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZSh1bWFwMSA9IG1lZGlhbih1bWFwMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bWFwMiA9IG1lZGlhbih1bWFwMikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gaHJDVCwgY29sb3IgPSBockNUKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKHBhbHM6OmFscGhhYmV0MigpLCBwYWxzOjpnbGFzYmV5KCkpICU+JSB1bm5hbWUoKSkgKyAKICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpvYnNfbmV1cmFsJG5vYnMgJT4lIAogIGdncGxvdChhZXMoeD11bWFwMSx5PXVtYXAyKSkgKwogIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IGFzLmZhY3RvcihockNUKSksIHBvaW50c2l6ZSA9IDIuMSwgYWxwaGEgPSAwLjUpICsKICAgIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IC4gJT4lIGdyb3VwX2J5KENULCBockNUKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZSh1bWFwMSA9IG1lZGlhbih1bWFwMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bWFwMiA9IG1lZGlhbih1bWFwMikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gaHJDVCwgY29sb3IgPSBockNUKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKHBhbHM6OmFscGhhYmV0MigpLCBwYWxzOjpnbGFzYmV5KCksIHBhbHM6OmFscGhhYmV0KCksIHBhbHM6OmJyZXdlci5zZXQxKDkpLCBwYWxzOjprZWxseSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIGZhY2V0X3dyYXAofkNUKQpgYGAKCiMjIGhjbHVzdApgYGB7ciwgZmlnLndpZHRoID0gMTgsIGZpZy5oZWlnaHQgPSAxMH0KcGIgPC0gcGIgPC0gZGF0YS50YWJsZTo6ZnJlYWQoJ34vZGF0YS9zY0VpYURfbW9kZWxpbmcvaHMxMTFfbWF0dXJlX2V5ZV9uZXVyYWwvaHMxMTFfbWF0dXJlX2V5ZV8yMDI0MDkyNF9mdWxsX19uZXVyYWw1MDAwaHZnXzIwMGVfNTBsLnBzZXVkb0J1bGsubGVpZGVuMy5jc3YuZ3onKQpjb2xuYW1lcyhwYikgPC0gZ3N1YigiXFwuXFxkKyIsIiIsY29sbmFtZXMocGIpKQpodmcgPC0gZGF0YS50YWJsZTo6ZnJlYWQoJ34vZGF0YS9zY0VpYURfbW9kZWxpbmcvaHMxMTFfbWF0dXJlX2V5ZV9uZXVyYWwvaHZnMjAwMC5jc3YuZ3onKVstMSxdCnJuYW1lcyA8LSBwYiRWMQpjbHVzdCA8LSBzdHJfZXh0cmFjdChybmFtZXMsICdcXGQrJykgJT4lIGFzLmludGVnZXIoKQpwYiA8LSBwYlssLTFdICU+JSBhcy5tYXRyaXgoKQpyb3cubmFtZXMocGIpIDwtIGFzLmNoYXJhY3RlcihjbHVzdCkKcGIgPC0gcGJbYXMuY2hhcmFjdGVyKG9ic19uZXVyYWwkbmxhYmVscyRsZWlkZW4zKSxdCgpwYl9ub3JtIDwtIG1ldGFtb1JwaDo6bm9ybWFsaXplX2RhdGEodChwYiksIHNhbXBsZV9zY2FsZSA9ICdjcG0nKSAlPiUgdCgpCgpwYl9ub3JtIDwtIHBiX25vcm1bLGh2ZyRWMl0KI3BiX25vcm0gPC0gcGJfbm9ybVssaHZnJFYyWyFodmckVjIgJWluJSBjKGNjX2dlbmVzLHJpYm9fZ2VuZXMpXV0KIyBodHRwczovL3N0YXRzLnN0YWNrZXhjaGFuZ2UuY29tL3F1ZXN0aW9ucy8zMTU2NS9jb21wdXRlLWEtY29zaW5lLWRpc3NpbWlsYXJpdHktbWF0cml4LWluLXIKc2ltIDwtIHBiX25vcm0gLyBzcXJ0KHJvd1N1bXMocGJfbm9ybSAqIHBiX25vcm0pKQpzaW0gPC0gc2ltICUqJSB0KHNpbSkKRF9zaW0gPC0gYXMuZGlzdCgxIC0gc2ltKQoKaGNsdXN0X3NpbSA8LSBoY2x1c3QoRF9zaW0sIG1ldGhvZCA9ICdhdmVyYWdlJykKCmhjbHVzdF9zaW0kbGFiZWxzIDwtIG9ic19uZXVyYWwkbmxhYmVscyAlPiUgcHVsbChsZWlkZW4zKQoKbGlicmFyeShnZ3RyZWUpCnAgPC0gZ2d0cmVlKGhjbHVzdF9zaW0pCnAkZGF0YSA8LSBwJGRhdGEgJT4lIGxlZnRfam9pbihvYnNfbmV1cmFsJG5sYWJlbHMsIGJ5ID0gYygibGFiZWwiID0gImxlaWRlbjMiKSkgJT4lCiAgbXV0YXRlKHRlY2hSYXRpbyA9IHJvdW5kKHRlY2hSYXRpbywgZGlnaXRzID0gMikpCnAgKyBsYXlvdXRfZGVuZHJvZ3JhbSgpICsKICBnZW9tX3RpcGxhYihhZXMobGFiZWwgPSBwYXN0ZShsYWJlbCwgQ1QsIHN0dWR5Q291bnQsIFRvdGFsQ291bnQsIHRlY2hSYXRpbywgc2VwID0gJyAtICcpLCBjb2xvciA9IENUKSkgKwogIHRoZW1lX2RlbmRyb2dyYW0ocGxvdC5tYXJnaW49bWFyZ2luKDE2LDE2LDMwMCwxNikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsKICBndWlkZXMoY29sb3I9Im5vbmUiKQoKCnAgPC0gZ2d0cmVlKGhjbHVzdF9zaW0pCnAkZGF0YSA8LSBwJGRhdGEgJT4lIGxlZnRfam9pbihvYnNfbmV1cmFsJG5sYWJlbHMgJT4lIG11dGF0ZShzdHVkaWVzID0gY2FzZV93aGVuKHN0dWR5Q291bnQgPT0xIH4gc3R1ZGllcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJtdWx0aXBsZSIpKSwgYnkgPSBjKCJsYWJlbCIgPSAibGVpZGVuMyIpKQoKcCArIGxheW91dF9kZW5kcm9ncmFtKCkgKwogIGdlb21fdGlwbGFiKGFlcyhsYWJlbCA9IHBhc3RlKGxhYmVsLCBDVCwgc3R1ZGllcywgc2VwID0gJyAtICcpLCBjb2xvciA9IENUKSkgKwogIGdlb21fdGlwcG9pbnQoYWVzKHNoYXBlID0gc3R1ZGllcyksIHNpemU9IDMpICsKICB0aGVtZV9kZW5kcm9ncmFtKHBsb3QubWFyZ2luPW1hcmdpbigxNiwxNiwzMDAsMTYpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSkgJT4lIHVubmFtZSgpKSArCiAgZ3VpZGVzKGNvbG9yPSJub25lIikKCgoKCmBgYAoKIyBPdXRwdXQKYGBge3J9CnNhdmUob2JzX25ldXJhbCwgZmlsZSA9ICdIdW1hbl9NYXR1cmVfRXllX2Z1bGxfX3N0YWdlNF9uZXVyYWwub2JzLmZyZWV6ZTIwMjQxMTA3LlJkYXRhJykKb2JzX25ldXJhbCRub2JzICU+JSBzZWxlY3QoYmFyY29kZSwgbGVpZGVuMywgQ1QsIGhyQ1QpICU+JSB3cml0ZV9jc3YoJ0h1bWFuX01hdHVyZV9FeWVfZnVsbF9fc3RhZ2U0X25ldXJhbC5DVGNhbGxzLmZyZWV6ZTIwMjQxMTA3LmNzdi5neicpCmBgYAo=